#-------------------------------------------------------------------------------
# Copyright (c) 2012 Huseyin BIYIK.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the GNU Public License v3.0
# which accompanies this distribution, and is available at
# http://www.gnu.org/licenses/gpl.html
# 
# Contributors:
#     Huseyin BIYIK - initial API and implementation
#-------------------------------------------------------------------------------
import socket, threading, random, uuid, time, logging, select
from messages import *
from audio import *
from call import *
from defs import logger
from network import *

debug = 0

class stack(threading.Thread):
	def __init__(self, api, dispatcher, addr, port=None):
		threading.Thread.__init__(self)
		self.api = api
		self.dispatcher = dispatcher
		self.ai = audio_interface()
		self.state = "idle"
		self.addr = addr
		if port is None:
			self.port = random.randint(10000, 20000)
		else:
			self.port = port
		self.net = interface(self.addr, self.port)
		self.loopback = False
		self.cm = manager()
		self.isclosing = False
		self.start()
		
	def connectrequest(self, localip, localport, destip, destport):
		self.state = "calling"
		callid = str(uuid.uuid1()) #unique call id
		msg = messages().types[0]
		args = [callid, localip, localport, destip, destport]
		msg = messages().to_str(msg, args)
		self.net.send(msg, destip, int(destport))
		logger.info("Requested connection to %s:%s", destip, destport)
		return callid
	
	def connectaccept(self, localip, localport, destip, destport, callid):
		self.state = "busy"
		msg = messages().types[1]
		args = [localip, localport, destip, destport, callid]
		msg = messages().to_str(msg, args)
		self.net.send(msg, destip, int(destport))
		logger.info("Accepted connection from %s:%s", destip, destport)

	def connectdecline(self, localip, localport, destip, destport):
		self.state = "idle"
		msg = messages().types[1]
		args = [localip, localport, destip, destport]
		msg = messages().to_str(msg, args)
		self.net.send(msg, destip, int(destport))
		logger.info("Declined connection from %s:%s", destip, destport)

	def senddata(self, callid, localip, localport, destip, destport, dtype, payload):
		msg = messages().types[4]
		args = [callid, localip, localport, destip, destport, dtype, payload]
		msg = messages().to_str(msg, args)
		self.net.send(msg, destip, int(destport))
		logger.debug("%s data sent to %s:%s", dtype, destip, destport)

	def run(self):
		bandwidth = {}
		while True:
			data, addr = self.net.receive() # buffer size is 1024 bytes
			if self.isclosing:
				break
			elif not data is None:
				#logger.debug("Client received message: %s", data)
				msg, args = messages().from_str(data)
				callid = args[0]
				laddr = args[1] #destip
				lport = int(args[2]) #destport
				raddr = args[3]
				rport = int(args[4])
				switch(msg){
					case messages().types[0]:
					## if connection requested

						dtype = "audio" #later to extend multiple types
						if self.addr == raddr  and self.port == rport):
							self.connectaccept(callid, raddr, rport, laddr, lport)
							self.cm.add_call(callid, raddr, rport, laddr, lport):
							self.cm.set_call_data(callid, dtype, True)
							logger.info("Call '%s' from %s:%d is accepted for %s and active", callid, laddr, lport, dtype)
							self.dispatcher(self.api, "callestablished", [self.cm.get_call(callid)])
						else:
							self.connectdecline(callid, raddr, rport, laddr, lport)
							logger.info("Client declined call from: %s:%s ", laddr, lport)
							self.dispatcher(self.api, "calldeclined"])
						continue
					
					case messages().types[1]:
					## if connection is accepted
						self.cm.add_call(callid, raddr, rport, laddr, lport):
						self.cm.set_call_data(callid, "audio", True)
						logger.info("Call '%s' from %s:%s is now authorized and active", callid, laddr, lport)
						self.dispatcher(self.api, "callestablished", [self.cm.get_call(callid)])

					case messages().types[2]:
					## if connection is declined
						self.cm.remove_call(callid):
						logger.info("Call '%s' to %s:%s declined", callid, laddr, lport])
						self.dispatcher(self.api, "calldeclined"])
						
					
				}



				if msg == messages().types[2]:
					


				## if data is received
				if msg == messages().types[4]:
	

					if self.loopback:
							self.senddata(args[0], args[3], args[4], args[1], args[2], args[5], args[6])
							logger.info("%d byte? %s loopbacked for call '%s'", asciisize(args[5]), args[5], callid)
			elif not self.loopback:
					for callid in self.cm.get_calls():
						if self.cm.is_call(callid):
#							for dtype in self.call.get_call_data(callid,None):
							if self.cm.get_call_data(callid, 'audio'):
								mic = self.ai.read()
								self.send_data([callid], 'audio', mic)
						else:
							self.cm.remove_call(callid)
#				if msg == messages().types[5]:

	def send_data(self, callids, dtype, payload):
		for callid in callids:
			call = self.cm.get_call(callid)
			if not call is None:
				self.senddata(callid, call.fromaddr, call.fromport, call.toaddr, call.toport, dtype, payload)

	def handle_data_bandwidth(self, callid, bandwidth, sequence):
#		self.send_data([callid],"bandwidth",sequence)
#		if not callid in bandwidth.keys():
#			bandwidth[callid]={"times":[],"sequences":[]}
		call = self.cm.get_call(callid)
		if not call is None:
			bandwidth = call.bandwidth
			if (len(bandwidth.keys()) != bw_seq_count and not sequence in bandwidth.keys()):
				bandwidth[sequence] = time.time()
			if (len(bandwidth.keys()) == bw_seq_count):
				#self.sendbandwidthreport(callid)
				prvs = None
				differences = []
				for sequence in sorted(bandwidth.keys()):
					if not prvs is None and prvs < bandwidth[sequence]:
						differences.append(bandwidth[sequence] - prvs)
					prvs = bandwidth[sequence]
				call.differences_down = differences
				logger.info("Avg downspeed for call '%s' : %d kbps", callid, call.get_downspeed())
				logger.info("Avg downlatency for call '%s' : %d ms", callid, call.get_downjitter())

				bandwidth = {}
			call.bandwidth = bandwidth

	def add_call_manual(self, callid, fromaddr, fromport, toaddr, toport):
		self.cm.add_call(callid, fromaddr, fromport, toaddr, toport)
	
	def close(self):
		self.isclosing = True
		self.net.close()
		self.ai.close()
		threading.Thread.join(self)
		return True
